// Copyright (c) 2011 Blue Onion Software, All rights reserved
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;

namespace BlueOnionSoftware.Bloget
{
    internal static class PingBackServer
    {
        private const int sourceUriNotFound = 0x0010;
        private const int sourceUriTargetNotFound = 0x0011;
        private const int targetUriNotFound = 0x0020;
        private const int pingbackAlreadyRegistered = 0x0030;
        private const int accessDenied = 0x0031;

        internal const string MethodPing = "pingback.ping";

        [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        internal static XmlRpcMethodResponse Ping(XmlRpcMethodCall methodCall, Bloget bloget)
        {
            if (methodCall == null)
            {
                throw new ArgumentNullException("methodCall");
            }

            if (bloget == null)
            {
                throw new ArgumentNullException("bloget");
            }

            if (methodCall.Name != MethodPing)
            {
                throw new InvalidOperationException("methodCall is not pingback.ping");
            }

            var methodResponse = new XmlRpcMethodResponse();
            var parameters = methodCall.Parameters;

            if (parameters.Length != 2)
            {
                methodResponse.FaultString = "pingback.ping requires two string parameters";
                return methodResponse;
            }

            if (bloget.Blog.EnablePingBackService == false)
            {
                methodResponse.FaultString = "Pingback Service not enabled";
                return methodResponse;
            }

            var source = parameters[0].Value as string;
            var target = parameters[1].Value as string;
            Log.PingBackReceived(bloget, source, target);

            // Is it a blog ml_post?
            var uri = new Uri(target, UriKind.Absolute);
            var guidRegex = new Regex(@"[A-Fa-f0-9]{8}-([A-Fa-f0-9]{4}-){3}[A-Fa-f0-9]{12}");
            var match = guidRegex.Match(uri.Query);

            if (match.Success == false)
            {
                methodResponse.FaultCode = targetUriNotFound;
                methodResponse.FaultString = "No valid blog link found";
                return methodResponse;
            }

            var id = new Guid(match.Value);

            if (bloget.Blog.Posts.Contains(id) == false)
            {
                methodResponse.FaultCode = targetUriNotFound;
                methodResponse.FaultString = "Link not found in blog";
                return methodResponse;
            }

            var post = bloget.Blog.Posts[id];

            if (post.Created.AddDays(bloget.Blog.ExpirePingBacks) < DateTime.UtcNow)
            {
                // Let them think they're already registered since there is no clear protocol
                // for expired maxPosts.
                methodResponse.FaultCode = accessDenied;
                methodResponse.FaultString = "Access denied (Expired)";
                return methodResponse;
            }

            // Does the target contain a source link?
            var request = (HttpWebRequest) WebRequest.Create(source);

            using (var response = (HttpWebResponse) request.GetResponse())
            {
                if (response.StatusCode != HttpStatusCode.OK)
                {
                    methodResponse.FaultCode = sourceUriNotFound;
                    methodResponse.FaultString = "Source URI not found";
                    return methodResponse;
                }

                using (var reader = new StreamReader(response.GetResponseStream()))
                {
                    var text = reader.ReadToEnd();

                    if (text.Contains(target) == false)
                    {
                        methodResponse.FaultCode = sourceUriTargetNotFound;
                        methodResponse.FaultString = "Target link not found in source Uri";
                        return methodResponse;
                    }
                }
            }

            var content = bloget.Blog.LoadContent(id);

            if (content.References.ContainsUrl(source))
            {
                methodResponse.FaultCode = pingbackAlreadyRegistered;
                methodResponse.FaultString = "Pingback already registered";
                return methodResponse;
            }

            if (content.References.Count >= bloget.Blog.MaximumPingsBacksPerPost)
            {
                methodResponse.FaultCode = accessDenied;
                methodResponse.FaultString = "Access denied (Max)";
                return methodResponse;
            }

            var pingBack = new Reference();
            pingBack.Link = source;
            content.References.Add(pingBack);
            bloget.Blog.SaveContent(id, content);

            try
            {
                if (bloget.Blog.EmailPingbacks)
                {
                    var subject = string.Format(CultureInfo.CurrentCulture,
                                                "Bloget Pingback Alert - Posting {0} {1}", id, post.Title);

                    var message = string.Format(CultureInfo.CurrentCulture,
                                                "Blog: {0}{3}Title: {1}{3}Referrer: {2}{3}",
                                                bloget.Blog.Title, post.Title, source, Environment.NewLine);

                    bloget.SendEmail(bloget.Blog.EmailTo, subject, message);
                }
            }

            catch (Exception ex)
            {
                Log.Exception(bloget, "Email Ping", ex);
            }

            var param = new XmlRpcValue("Pingback received. Thanks!");
            methodResponse.AddParam(param);
            return methodResponse;
        }
    }
}